﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;

namespace Qreed.Windows.ComponentModel
{
    /// <summary>
    /// The property descriptor for a <see cref="PropertyBagItem"/>
    /// </summary>
    public class PropertyBagItemDescriptor : PropertyDescriptor
    {
        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyBagItemDescriptor"/> class.
        /// </summary>
        /// <param name="propertyBag">The property bag.</param>
        /// <param name="item">The item.</param>
        /// <param name="attributes">The attributes.</param>
        internal PropertyBagItemDescriptor(PropertyBagItem item, Attribute[] attributes)
            : base(item.DisplayName, attributes)
        {
            _item = item;

            InitAttributes();
        }

        private void InitAttributes()
        {
            List<Attribute> attribList = Item.GetItemAttributes();

            Attribute[] attribs = new Attribute[attribList.Count];
            attribList.CopyTo(attribs);

            AttributeArray = attribs;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the property bag.
        /// </summary>
        /// <value>The property bag.</value>
        public PropertyBag PropertyBag
        {
            get { return _item.Bag; }
        }

        private PropertyBagItem _item;
        /// <summary>
        /// Gets the <see cref="PropertyBagItem"/>.
        /// </summary>
        /// <value>The item.</value>
        public PropertyBagItem Item
        {
            get { return _item; }
        }

        #endregion

        #region PropertyDescriptor

        /// <summary>
        /// When overridden in a derived class, returns whether resetting an object changes its value.
        /// </summary>
        /// <param name="component">The component to test for reset capability.</param>
        /// <returns>
        /// true if resetting the component changes its value; otherwise, false.
        /// </returns>
        public override bool CanResetValue(object component)
        {
            if (Item.DefaultValue == null)
                return false;
            else
                return !GetValue(component).Equals(Item.DefaultValue);
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the component this property is bound to.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.Type"></see> that represents the type of component this property is bound to. When the <see cref="M:System.ComponentModel.PropertyDescriptor.GetValue(System.Object)"></see> or <see cref="M:System.ComponentModel.PropertyDescriptor.SetValue(System.Object,System.Object)"></see> methods are invoked, the object specified might be an instance of this type.</returns>
        public override Type ComponentType
        {
            get { return Item.GetType(); }
        }

        /// <summary>
        /// When overridden in a derived class, gets a value indicating whether this property is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the property is read-only; otherwise, false.</returns>
        public override bool IsReadOnly
        {
            get { return Item.IsReadOnly; }
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the property.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.Type"></see> that represents the type of the property.</returns>
        public override Type PropertyType
        {
            get { return Item.Type; }
        }

        /// <summary>
        /// When overridden in a derived class, resets the value for this property of the component to the default value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be reset to the default value.</param>
        public override void ResetValue(object component)
        {
            SetValue(component, Item.DefaultValue);
        }

        /// <summary>
        /// When overridden in a derived class, sets the value of the component to a different value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be set.</param>
        /// <param name="value">The new value.</param>
        public override void SetValue(object component, object value)
        {
            //Debug.WriteLine("PropertyBagItemDescriptor.SetValue: component " + (component == null ? "null" : "\"" + component.ToString() + " (" + component.GetType().Name + ")") + "value \"" + value + "\" (" + value.GetType().Name + ") item" + Item.GetType().Name);
            Item.SetValueInternal(value);
        }

        /// <summary>
        /// When overridden in a derived class, gets the current value of the property on a component.
        /// </summary>
        /// <param name="component">The component with the property for which to retrieve the value.</param>
        /// <returns>
        /// The value of a property for a given component.
        /// </returns>
        public override object GetValue(object component)
        {
            object value = Item.GetValueInternal();
            
            //Debug.WriteLine("PropertyBagItemDescriptor.GetValue: component " + (component == null ? "null" : "\"" + component.ToString() + " (" + component.GetType().Name + ")") + "value \"" + value + "\" (" + value.GetType().Name + ") item" + Item.GetType().Name);

            return value;
        }

        /// <summary>
        /// When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted.
        /// </summary>
        /// <param name="component">The component with the property to be examined for persistence.</param>
        /// <returns>
        /// true if the property should be persisted; otherwise, false.
        /// </returns>
        public override bool ShouldSerializeValue(object component)
        {
            object val = GetValue(component);

            if (Item.DefaultValue == null && val == null)
                return false;
            else
                return !val.Equals(Item.DefaultValue);
        }

        public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
        {
            // Never called?
            return base.GetChildProperties(instance, filter);
        }

        /// <summary>
        /// Gets the type converter for this property.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.ComponentModel.TypeConverter"></see> that is used to convert the <see cref="T:System.Type"></see> of this property.</returns>
        /// <PermissionSet><IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/></PermissionSet>
        public override TypeConverter Converter
        {
            get
            {
                if (Item.TypeConverter != null)
                    return Item.TypeConverter;

                return base.Converter;
            }
        }

        /// <summary>
        /// Gets an editor of the specified type.
        /// </summary>
        /// <param name="editorBaseType">The base type of editor, which is used to differentiate between multiple editors that a property supports.</param>
        /// <returns>
        /// An instance of the requested editor type, or null if an editor cannot be found.
        /// </returns>
        public override object GetEditor(Type editorBaseType)
        {
            if (Item.TypeEditor != null)
                return Item.TypeEditor;

            return base.GetEditor(editorBaseType);
        }

        #endregion
    }
}
